namespace SharpTalk.Client.WpfApplication
{
    using System;
    using System.Diagnostics;
    using System.Linq;
    using System.Reflection;
    using Core;
    using Core.Base;
    using Core.Delegates;
    using Core.Diagnostic;
    using Core.Enums;
    using Core.EventArgs;
    using Core.Interfaces;
    using Core.Services.Requests;
    using Services;

    public sealed class MessageHandlers
    {
        private IMessageHandler[] _handlers = null;

        public MessageHandlers()
        {
        }

        public void AddHandler(IMessageHandler handler)
        {
            if(_handlers == null)
                _handlers = new IMessageHandler[]{ handler };
            else
            {
                Array.Resize(ref _handlers, _handlers.Length+1);
                _handlers[_handlers.Length-1] = handler;
            }
        }

        public void HandleMessage(MessageSendRequestDetails item, MessageHandleEventArgs e)
        {
            if(_handlers==null) return;
            foreach (var messageHandler in _handlers.Where(h=>h.CanHandle(item.Type)))
                messageHandler.HandleMessage(item,e);
        }

        public bool HasHandler(MessageTypeEnum messageType)
        {
            return _handlers!=null &&  _handlers.Where(h => h.CanHandle(messageType)).Any();
        }

        public void Clear()
        {
            _handlers = null;
        }
    }

    public abstract class MessageHandlerBase<T> : MessageHandlerBase, IMessageHandler<T>
    {
        protected readonly IApplicationServices Services;

        /// <summary>
        /// Initialiazes a new instance
        /// </summary>
        /// <param name="traceProvider"></param>
        protected MessageHandlerBase(ITraceProvider traceProvider) 
            : base(traceProvider)
        {
        }

        /// <summary>
        /// Initialiazes a new instance
        /// </summary>
        /// <param name="traceProvider"></param>
        /// <param name="applicationServices"></param>
        protected MessageHandlerBase(ITraceProvider traceProvider, IApplicationServices applicationServices)
            : this(traceProvider)
        {
            if (applicationServices == null) throw new ArgumentNullException("applicationServices");
            Services = applicationServices;
        }

        public override void HandleMessage(object message, MessageHandleEventArgs e)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            if (e.Handled) return;
            if (message == null) throw new ArgumentNullException("message");
            HandleMessage((T)message,e);
        }

        public abstract void HandleMessage(T item, MessageHandleEventArgs e);

        internal virtual void AddMessage(Discussion discussion, SimpleMessage message)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());

            if(discussion==null || message ==null) return;

            if (Services.UI.CheckAccess())
                discussion.AddMessage(message);
            else
                Services.UI.Invoke(new ActionDelegate<SimpleMessage>(discussion.AddMessage), message);
        }

        internal Discussion FindDiscussion(Guid chatroom)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            return Services.Discussions.Where(d => d.Chatroom.Guid == chatroom).FirstOrDefault();
        }

        internal User FindContact(Guid user)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            return Services.Contacts.Where(c => c.Guid == user).FirstOrDefault();
        }
    }

    public sealed class SimpleMessageHandler : MessageHandlerBase<MessageSendRequestDetails>
    {
        /// <summary>
        /// Initializes a new instance
        /// </summary>
        /// <param name="traceProvider"></param>
        /// <param name="applicationServices"></param>
        public SimpleMessageHandler(ITraceProvider traceProvider, IApplicationServices applicationServices)
            : base(traceProvider, applicationServices)
        {
        }

        public SimpleMessageHandler(IApplicationServices applicationServices) 
            : this(null, applicationServices)
        {
        }

        public override bool CanHandle(MessageTypeEnum messageType)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            return messageType == MessageTypeEnum.UserMessage;
        }

        public override void HandleMessage(MessageSendRequestDetails item, MessageHandleEventArgs e)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            if(!CanHandle(item.Type)) return;

            var discussion = FindDiscussion(item.ChatRoom);
            if (discussion == null) return;
            var msg = item.Message as SimpleMessage;
            if(msg ==null) return;

            AddMessage(discussion, msg);
            e.Handled = true;
        }
    }

    public sealed class UserNotificationMessageHandler : MessageHandlerBase<MessageSendRequestDetails>
    {
        /// <summary>
        /// Initializes a new instance
        /// </summary>
        /// <param name="traceProvider"></param>
        /// <param name="applicationServices"></param>
        public UserNotificationMessageHandler(ITraceProvider traceProvider, IApplicationServices applicationServices) 
            : base(traceProvider, applicationServices)
        {
        }

        public UserNotificationMessageHandler(IApplicationServices applicationServices) 
            : this(null, applicationServices)
        {
        }

        public override void HandleMessage(MessageSendRequestDetails item, MessageHandleEventArgs e)
        {
            if (!CanHandle(item.Type)) return;
            
            var userNotif = item.Message as UserNotificationMessage;
            if(userNotif == null) return;
            
            var discussion = FindDiscussion(item.ChatRoom);
            if (discussion == null) return;

            switch (item.Type)
            {
                case MessageTypeEnum.UserJoin :
                case MessageTypeEnum.UserLeft :
                        HandleUserJoinOrLeft(discussion, item.Type, userNotif.Content);
                        e.Handled = true;
                    break;
                case MessageTypeEnum.UserChange:
                        HandleUserChange(userNotif.Content);
                        e.Handled = true;
                    break;
                case MessageTypeEnum.UserDeleted :
                        HandleUserDelete(userNotif.Content);
                        e.Handled = true;
                    break;
            }
            
        }

        public override bool CanHandle(MessageTypeEnum messageType)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            return messageType == MessageTypeEnum.UserJoin ||
                   messageType == MessageTypeEnum.UserLeft ||
                   messageType == MessageTypeEnum.UserChange ||
                   messageType == MessageTypeEnum.UserDeleted;
        }

        private void HandleUserChange(User user)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            if(user==null) return;

            // Update discussions
            foreach (var discussion in Services.Discussions.Where(disc => disc.Users.Contains(user)))
            {
                var old = (User)discussion.Users[discussion.Users.IndexOf(user)].Clone();

                // Updates user details in every discussion
                if (Services.UI.CheckAccess())
                    UpdateDiscussionUser(discussion,user);
                else
                    Services.UI.Invoke(new ActionDelegate<bool, Discussion, User>(UpdateDiscussionUser), discussion, user);
                
                // post message about his change
                if (!string.Equals(old.FullName, user.FullName, StringComparison.InvariantCultureIgnoreCase))
                    AddMessage(discussion, new SimpleMessage()
                                               {
                                                   Content = string.Format(Resources.Messages.message_user_changed,old.FullName, user.FullName),
                                                   DateTime = DateTime.Now
                                               });
            }

            // Update Contacts
            if (Services.UI.CheckAccess())
                UpdateContact(user);
            else
                Services.UI.Invoke(new ActionDelegate<User>(UpdateContact),user);
        }

        private void HandleUserDelete(User user)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            if(user == null) return;

            // Remove User from discussions
            foreach (var discussion in Services.Discussions.Where(disc => disc.Users.Contains(user)))
            {
                if (Services.UI.CheckAccess())
                    discussion.Users.Remove(user);
                else
                    Services.UI.Invoke(new ActionDelegate<bool, User>(discussion.Users.Remove), user);
            }

            // Remove User from Contacts
            if (Services.UI.CheckAccess())
                Services.Contacts.Remove(user);
            else
                Services.UI.Invoke(new ActionDelegate<bool, User>(Services.Contacts.Remove), user);
        }

        private void HandleUserJoinOrLeft(Discussion discussion, MessageTypeEnum type, User user)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            AddMessage(discussion, new SimpleMessage()
            {
                Content =
                    string.Format(
                        type == MessageTypeEnum.UserJoin
                            ? Resources.Messages.message_user_joined
                            : Resources.Messages.message_user_left,
                        user.FullName),
                DateTime = DateTime.Now
            });

            switch (type)
            {
                case MessageTypeEnum.UserLeft :
                    if (Services.UI.CheckAccess())
                        discussion.Users.Remove(user);
                    else
                        Services.UI.Invoke(new ActionDelegate<bool, User>(discussion.Users.Remove), user);
                    break;
                case MessageTypeEnum.UserJoin :
                    if (Services.UI.CheckAccess())
                        discussion.Users.Add(user);
                    else
                        Services.UI.Invoke(new ActionDelegate<User>(discussion.Users.Add), user);
                    break;
            }

        }
        
        private void UpdateContact(User contact)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            var user = FindContact(contact.Guid);
            if (user == null || user == Services.CurrentUser) return;

            UpdateUserDetails(user, contact, true);
        }

        private bool UpdateDiscussionUser(Discussion discussion, User user)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            var old = discussion.Users[discussion.Users.IndexOf(user)];
            UpdateUserDetails(old, user, true);
            return true;
        }

        private void UpdateUserDetails(UserBase oldUser, UserBase newUser, bool notify)
        {
            if (ShouldTrace(TraceEventType.Verbose)) TraceMethodCall(MethodBase.GetCurrentMethod());
            oldUser.IsNotifying = false;

            oldUser.FirstName = newUser.FirstName;
            oldUser.LastName = newUser.LastName;
            oldUser.NickName = newUser.NickName;
            oldUser.Image = newUser.Image;
            oldUser.Status = newUser.Status;
            oldUser.Uri = newUser.Uri;

            oldUser.IsNotifying = true;

            if (notify) oldUser.Refresh();
        }
    }
}